﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Basmus.DataStructures;
using Basmus.Patterns;
using Basmus.Reflection;

namespace Basmus.BindingFramework
{
	public class AttributiveNamedTuples : INamedTuples
	{
		#region ICompositor Members

		public IEnumerable<string> Names { get; private set; }
		public Func<string, Type> ElementType { get; private set; }
		public Action<object, string, Tuple> SetTuple { get; private set; }
		public Func<object, string, Tuple> GetTuple { get; private set; }

		#endregion

		private LazyLoading<string, Delegate> getTupleCache;
		private LazyLoading<string, Delegate> setTupleCache;

		public AttributiveNamedTuples(Type targetType)
		{
			var attrs = targetType.PropertiesWithNonMultipleAttribute<NamedTupleAttribute>(false).ToDictionary(x => x.Second.Name);
			Names = attrs.Keys;
			ElementType = s => attrs[s].Second.ElementType;
			Func<string, Delegate> getTupleCacheFunc = name =>
				{
					var elementType = attrs[name].Second.ElementType;
					var propertyGetMethod = attrs[name].First.GetGetMethod();
					var constructorInfo = typeof(Tuple).GetConstructor(new Type[3] { typeof(Type), typeof(object), typeof(bool) });

					var target = Expression.Parameter(targetType, "target");
					var target_Property_GetEntity = Expression.Call(target,propertyGetMethod);
					var typeConstant = Expression.Constant(elementType, typeof(Type));

					Type realType;
					Expression tuple;
					bool f = attrs[name].First.PropertyType.IEnumerableImplemented(out realType);
					if (f && realType == elementType)
					{
						var trueConstant = Expression.Constant(true, typeof(bool));
						tuple = Expression.New(constructorInfo, typeConstant, target_Property_GetEntity, trueConstant);

					}
					else
					{
						var falseConstant = Expression.Constant(false, typeof(bool));
						tuple = Expression.New(constructorInfo, typeConstant, target_Property_GetEntity, falseConstant);
					}
					var lambda = Expression.Lambda(tuple, target);
					return lambda.Compile();
				};
			getTupleCache = getTupleCacheFunc.ToLazyLoading();
			GetTuple = (o, s) => (Tuple)getTupleCache[s].DynamicInvoke(o);
			Func<string, Delegate> setTupleCacheFunc = name =>
				{
					var elementType = attrs[name].Second.ElementType;
					var propertySetMethod = attrs[name].First.GetSetMethod();

					var tuple = Expression.Parameter(typeof(Tuple), "tuple");
					var cast_Tuple = Expression.Call(typeof(Basmus.DataStructures.Generic.ITupleExtension), "Cast", new Type[1] { elementType }, tuple);
					var cast_Tuple_Element = Expression.Property(cast_Tuple, "Element");
					var cast_Tuple_Elements = Expression.Property(cast_Tuple, "Elements");
					var cast_Tuple_IsEnumerable = Expression.Property(cast_Tuple, "IsEnumerable");
					var target = Expression.Parameter(targetType, "target");
					var target_Property = Expression.Property(target, attrs[name].First);
					MethodCallExpression target_Property_SetElement;
					MethodCallExpression target_Property_SetElements;
					Type realType;
					bool f = attrs[name].First.PropertyType.IEnumerableImplemented(out realType);
					if (f && realType == elementType)
					{
						target_Property_SetElements = Expression.Call(target, propertySetMethod, cast_Tuple_Elements);
						var empty = Expression.Call(typeof(Enumerable), "Empty", new Type[] { elementType });
						var empty_DefaultIfEmpty = Expression.Call(typeof(Enumerable), "DefaultIfEmpty", new Type[1] { elementType }, empty, cast_Tuple_Element);
						target_Property_SetElement = Expression.Call(target, propertySetMethod, empty_DefaultIfEmpty);
					}
					else
					{
						target_Property_SetElement = Expression.Call(target, propertySetMethod, cast_Tuple_Element);
						var enumerable_First = Expression.Call(typeof(Enumerable), "First", new Type[] { elementType }, cast_Tuple_Elements);
						target_Property_SetElements = Expression.Call(target, propertySetMethod, enumerable_First);
						
					}
					var condition = Expression.Condition(cast_Tuple_IsEnumerable, target_Property_SetElements, target_Property_SetElement);
					var lambdaEx = Expression.Lambda(condition, target, tuple);
					return lambdaEx.Compile();
				};
			setTupleCache = setTupleCacheFunc.ToLazyLoading();
			SetTuple = (o, s, t) => setTupleCache[s].DynamicInvoke(o, t);
		}
	}
}
